﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Windows;
using MediaAssistant.DAL.Constants;
using System.Linq;
using MediaAssistant.DAL.Helper;
using MefBasic.Extensions;
using Newtonsoft.Json.Linq;

namespace MediaAssistant.DAL
{
    [Export]
    public class DatabaseManager
    {
        public DatabaseManager()
        {
            Context = new MediaAssistantSqliteDbEntities();
        }

        public MediaAssistantSqliteDbEntities Context { get; private set; }

        public static readonly object LockObject = new object();
        private static DatabaseManager _instance;
        private Movie[] _processingMovies;

        public static DatabaseManager Instance
        {
            get
            {
                if (_instance == null)
                {
                    Debug.WriteLine("Waiting for lock Instance");
                    lock (LockObject)
                    {
                        Debug.WriteLine("lock Instance");
                        if (_instance == null)
                        {
                            _instance = new DatabaseManager();
                        }
                        Debug.WriteLine("Unlock Instance");
                    }
                }
                return _instance;
            }
        }

        public bool TurnOffSaving
        {
            get { return Context.TurnOffSaving; }
            set { Context.TurnOffSaving = value; }
        }

        public void RemoveOldVersionLibraryItems(string type)
        {
            Debug.WriteLine("Waiting for lock RemoveOldVersionLibraryItems");
            lock (LockObject)
            {
                Debug.WriteLine("lock RemoveOldVersionLibraryItems");
                foreach (var libraryItem in Context.LibraryItems.Where(l => l.Type == type).ToArray())
                {
                    Context.RemoveLibraryItem(libraryItem);
                }
                Context.Save();
                Debug.WriteLine("Unlock RemoveOldVersionLibraryItems");
            }
        }

        public void LinkOldVersionMusics()
        {
            Debug.WriteLine("Waiting for lock LinkOldVersionMusics");
            lock (LockObject)
            {
                Debug.WriteLine("lock LinkOldVersionMusics");
                foreach (var music in Context.Musics)
                {
                    Context.LinkMusic(music);
                }
                Context.Save();
                Debug.WriteLine("Unlock LinkOldVersionMusics");
            }
        }

        public void ConvertOldVersionMusicLibrary()
        {
            Debug.WriteLine("Waiting for lock ConvertOldVersionMusicLibrary");
            lock (LockObject)
            {
                Debug.WriteLine("lock ConvertOldVersionMusicLibrary");
                RemoveOldVersionLibraryItems(LibraryItemType.Genre);
                RemoveOldVersionLibraryItems(LibraryItemType.Album);
                RemoveOldVersionLibraryItems(LibraryItemType.Artist);
                RemoveOldVersionLibraryItems(LibraryItemType.Composer);
                LinkOldVersionMusics();
                Debug.WriteLine("Unlock ConvertOldVersionMusicLibrary");
            }
        }

        public void Delete(AlternativeLocation alternativeLocation)
        {
            Debug.WriteLine("Waiting for lock Delete(AlternativeLocation alternativeLocation)");
            lock (LockObject)
            {
                Debug.WriteLine("lock Delete(AlternativeLocation alternativeLocation)");
                Context.AlternativeLocations.DeleteObject(alternativeLocation);
                Context.Save();
                Debug.WriteLine("Unlock Delete(AlternativeLocation alternativeLocation)");
            }
        }

        public void SaveScannedFolders(IEnumerable<ScannedDirectory> scannedDirectories)
        {
            Debug.WriteLine("Waiting for lock SaveScannedFolders");
            lock (LockObject)
            {
                Debug.WriteLine("lock SaveScannedFolders");
                foreach (var scannedDirectory in Context.ScannedDirectories.ToArray())
                {
                    Context.ScannedDirectories.DeleteObject(scannedDirectory);
                }
                Context.Save();
                foreach (var scannedDirectory in scannedDirectories)
                {
                    var fullPath = scannedDirectory.FullPath;
                    if (Context.ScannedDirectories.Any(sd => sd.FullPath == fullPath) == false)
                    {
                        Context.ScannedDirectories.AddObject(scannedDirectory);
                    }
                }
                Context.Save();
                Debug.WriteLine("Unlock SaveScannedFolders");
            }
        }

        public IEnumerable<ScannedDirectory> GetScanDirectories()
        {
            Debug.WriteLine("Waiting for lock GetScanDirectories");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetScanDirectories");
                var scannedDirectories = Context.ScannedDirectories.ToArray();
                Debug.WriteLine("Unlock GetScanDirectories");
                return scannedDirectories;
            }
        }

        public IEnumerable<string> GetImportedFileNames()
        {
            Debug.WriteLine("Waiting for lock GetImportedFileNames");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetImportedFileNames");
                var existingFiles = new List<string>();
                existingFiles.AddRange(Context.Movies.Where(m => string.IsNullOrEmpty(m.FullPath)).Select(m => m.FullPath));
                existingFiles.AddRange(Context.AlternativeLocations.Select(l => l.Location));
                existingFiles.AddRange(Context.Musics.Select(m => m.FullPath));
                Debug.WriteLine("Unlock GetImportedFileNames");
                return existingFiles.Distinct();
            }
        }

        public void AddMusicFile(string fileName)
        {
            Debug.WriteLine("Waiting for lock AddMusicFile");
            lock (LockObject)
            {
                Debug.WriteLine("lock AddMusicFile");
                Context.AddMusicFile(fileName);
                Context.Save();
                Debug.WriteLine("unlock AddMusicFile");
            }
        }

        public void AddMovieFile(string fileName)
        {
            Debug.WriteLine("Waiting for lock AddMovieFile");
            lock (LockObject)
            {
                Debug.WriteLine("lock AddMovieFile");
                Context.AddMovieFile(fileName);
                Context.Save();
                Debug.WriteLine("Unlock AddMovieFile");
            }
        }

        public int GetMusicCount()
        {
            Debug.WriteLine("Waiting for lock GetMusicCount");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetMusicCount");
                var musicCount = Context.Musics.Count();
                Debug.WriteLine("Unlock GetMusicCount");
                return musicCount;
            }
        }

        public IEnumerable<Music> GetAllMusics()
        {
            Debug.WriteLine("Waiting for lock GetAllMusics");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetAllMusics");
                var musics = Context.Musics.ToArray();
                Debug.WriteLine("Unlock GetAllMusics");
                return musics;
            }
        }

        public void CreateFixedLibraryItems()
        {
            Debug.WriteLine("Waiting for lock CreateFixedLibraryItems");
            lock (LockObject)
            {
                Debug.WriteLine("lock CreateFixedLibraryItems");
                Context.CreateFixedLibraryItems();
                Context.Save();
                Debug.WriteLine("Unlock CreateFixedLibraryItems");
            }
        }

        public void UpdateIMDBMustWatchList()
        {
            Debug.WriteLine("Waiting UpdateIMDBMustWatchList");
            lock (LockObject)
            {
                Debug.WriteLine("Lock UpdateIMDBMustWatchList");
                var imdbMovieList = Context.GetOrCreateFixedLibraryItem(LibraryItemType.IMDbMustWatchList);
                if (imdbMovieList.Movies.Count < 250)
                {
                    var imdb250MovieFile = ResourceHelper.ReadText("MediaAssistant.DAL.IMDB250.txt");
                    var imdbIds = imdb250MovieFile.Split("\r\n".ToCharArray()).Where(s => s != string.Empty);
                    var existingMovies = GetExistingMovieDictionary();
                    foreach (var imdbId in imdbIds)
                    {
                        Movie movie;
                        if(existingMovies.ContainsKey(imdbId))
                        {
                            movie = existingMovies[imdbId];
                        }
                        else
                        {
                            movie = new Movie
                            {
                                Id = Guid.NewGuid().ToString(),
                                IMDBId = imdbId,
                                Status = MovieStatus.Pending,
                                WeightedRating = 0,
                                CreateDate = DateTime.Now
                            };
                            Context.Movies.AddObject(movie);    
                        }
                        if (imdbMovieList.Movies.Any(m => m.IMDBId == imdbId) == false)
                        {
                            imdbMovieList.Movies.Add(movie);
                        }
                    }
                    Context.Save();
                }
                Debug.WriteLine("Unlock UpdateIMDBMustWatchList");
            }
        }

        public void ImportIMDbMovies(IWaitScreenService waitScreen)
        {
            Debug.WriteLine("Waiting ImportIMDbMovies");
            lock (LockObject)
            {
                Debug.WriteLine("Lock ImportIMDbMovies");
                var movieFile = ResourceHelper.ReadText("MediaAssistant.DAL.IMDBMovies.txt");
                var jsonDataCollection = movieFile.Split("\r\n".ToCharArray()).Where(s => s != string.Empty);
                waitScreen.Show(jsonDataCollection.Count(), 0);
                Application.Current.DoEvents();
                var existingMovies = GetExistingMovieDictionary();
                Context.TurnOffSaving = true;
                foreach (var jsonData in jsonDataCollection)
                {
                    waitScreen.AdvanceProgress();
                    Application.Current.DoEvents();
                    var jsonObject = JObject.Parse(jsonData);

                    if (MediaHelper.HasError(jsonObject))
                    {
                        continue;
                    }
                    var imdbId = (string)jsonObject.SelectToken("imdbID");
                    if (existingMovies.ContainsKey(imdbId))
                    {
                        continue;
                    }
                    var movie = new Movie
                    {
                        Id = Guid.NewGuid().ToString(),
                        Status = MovieStatus.Done,
                        WeightedRating = 0,
                        CreateDate = DateTime.Now
                    };
                    Context.Movies.AddObject(movie);
                    Context.UpdateMovie(movie, jsonObject, false);
                }
                Context.TurnOffSaving = false;
                Context.Save();
                Debug.WriteLine("Unlock ImportIMDbMovies");
            }
        }

        private Dictionary<string, Movie> GetExistingMovieDictionary()
        {
            var existingMovies = Context.Movies.Where(m => string.IsNullOrEmpty(m.IMDBId) == false).ToDictionary(m => m.IMDBId);
            return existingMovies;
        }

        public IEnumerable<LibraryItem> GetRootLibraryItems()
        {
            Debug.WriteLine("Waiting for lock GetRootLibraryItems");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetRootLibraryItems");
                var rootItems = new List<LibraryItem>();
                var count = 0;
                foreach (var libraryItem in Context.LibraryItems)
                {
                    if (!LibraryItemType.IsRootItem(libraryItem.Type)) continue;
                    libraryItem.IsExpanded = true;
                    rootItems.Add(libraryItem);
                    if (++count == LibraryItemType.RootItemsCount)
                        break;
                }
                Debug.WriteLine("Unlock GetRootLibraryItems");
                return rootItems;
            }
        }

        public IEnumerable<LibraryItem> GetAllPlaylists()
        {
            Debug.WriteLine("Waiting for lock GetAllPlaylists");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetAllPlaylists");
                var libraryItems = Context.LibraryItems.Where(o => o.Type.Equals(LibraryItemType.Playlist) && o.Id != LibraryItemType.LastImportedPlaylist).OrderBy(p => p.Title).ToArray();
                Debug.WriteLine("Unlock GetAllPlaylists");
                return libraryItems;
            }
        }

        public void UpdateNewMovieTitle(bool showDrivesMoviesOnly, Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock UpdateNewMovieTitle");
            lock (LockObject)
            {
                Debug.WriteLine("lock UpdateNewMovieTitle");
                Context.UpdateNewMovieTitle(showDrivesMoviesOnly, selectedProfile);
                Context.Save();
                Debug.WriteLine("unlock UpdateNewMovieTitle");
            }
        }

        public ProfileMovie GetOrCreateProfileMovie(Profile selectedProfile, Movie movie)
        {
            Debug.WriteLine("Waiting for lock GetOrCreateProfileMovie");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetOrCreateProfileMovie");
                var profileMovie = Context.GetOrCreateProfileMovie(movie, selectedProfile);
                Context.Save();
                Debug.WriteLine("unlock GetOrCreateProfileMovie");
                return profileMovie;
            }
        }

        public void AddFile(string fileName)
        {
            Debug.WriteLine("Waiting for lock AddFile");
            lock (LockObject)
            {
                Debug.WriteLine("lock AddFile");
                Context.AddFile(fileName);
                Context.Save();
                Debug.WriteLine("unlock AddFile");
            }
        }

        public LibraryItem GetFixedLibraryItem(string type)
        {
            Debug.WriteLine("Waiting for lock GetOrCreateFixedLibraryItem(string type)");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetOrCreateFixedLibraryItem(string type)");
                var item = Context.GetOrCreateFixedLibraryItem(type);
                Context.Save();
                Debug.WriteLine("unlock GetOrCreateFixedLibraryItem(string type)");
                return item;
            }
        }

        public LibraryItem GetLastImportedPlaylist()
        {
            Debug.WriteLine("Waiting for lock GetLastImportedPlaylist");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetLastImportedPlaylist");
                var lastImportedPlaylist = GetFixedLibraryItem(LibraryItemType.LastImportedPlaylist);
                Debug.WriteLine("unlock GetLastImportedPlaylist");
                return lastImportedPlaylist;
            }
        }

        public void ClearLastImportedPlaylist()
        {
            Debug.WriteLine("Waiting for lock ClearLastImportedPlaylist");
            lock (LockObject)
            {
                Debug.WriteLine("lock ClearLastImportedPlaylist");
                Context.GetOrCreateFixedLibraryItem(LibraryItemType.LastImportedPlaylist).Musics.Clear();
                Context.Save();
                Debug.WriteLine("unlock ClearLastImportedPlaylist");
            }
        }

        public ProfileMusic GetProfileMusic(Music music, Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock GetProfileMusic(Music music, Profile selectedProfile)");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetProfileMusic(Music music, Profile selectedProfile)");
                if (selectedProfile == null)
                {
                    Debug.WriteLine("unlock GetProfileMusic(Music music, Profile selectedProfile)");
                    return null;
                }
                var profileMusic = music.ProfileMusics.FirstOrDefault(p => p.Profile == selectedProfile);
                if (profileMusic == null)
                {
                    profileMusic = new ProfileMusic { Music = music, Profile = selectedProfile, Score = 1 };
                    Context.ProfileMusics.AddObject(profileMusic);
                    Context.Save();
                }
                Debug.WriteLine("unlock GetProfileMusic(Music music, Profile selectedProfile)");
                return profileMusic;
            }
        }

        public IEnumerable<Movie> UpdateProcessingMovies()
        {
            Debug.WriteLine("Waiting for lock UpdateProcessingMovies");
            lock (LockObject)
            {
                Debug.WriteLine("lock UpdateProcessingMovies");
                _processingMovies = Context.GetProcessingMovies().ToArray();
                Debug.WriteLine("unlock UpdateProcessingMovies");
                return _processingMovies;
            }
        }
        public IEnumerable<Movie> GetProcessingMovies()
        {
            Debug.WriteLine("Waiting for lock GetProcessingMovies");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetProcessingMovies");
                var processingMovies = _processingMovies;
                Debug.WriteLine("unlock GetProcessingMovies");
                return processingMovies;
            }
        }

        public IEnumerable<Movie> GetFailedMovies()
        {
            Debug.WriteLine("Waiting for lock GetFailedMovies");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetFailedMovies");
                var failedMovies = Context.GetFailedMovies().ToArray();
                Debug.WriteLine("unlock GetFailedMovies");
                return failedMovies;
            }
        }

        public IEnumerable<Movie> GetNewMovies(Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock GetNewMovies(Profile selectedProfile)");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetNewMovies(Profile selectedProfile)");
                var newMovies = Context.GetNewMovies(selectedProfile).ToArray();
                Debug.WriteLine("unlock GetNewMovies(Profile selectedProfile)");
                return newMovies;
            }
        }

        public IEnumerable<Movie> GetWatchedMovies(Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock GetWatchedMovies");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetWatchedMovies");
                var watchedMovies = Context.GetWatchedMovies(selectedProfile).ToArray();
                Debug.WriteLine("unlock GetWatchedMovies");
                return watchedMovies;
            }
        }

        public IEnumerable<Movie> GetWishListMovie(Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock GetWishListMovie");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetWishListMovie");
                var wishListMovie = Context.GetWishListMovie(selectedProfile).ToArray();
                Debug.WriteLine("unlock GetWishListMovie");
                return wishListMovie;
            }
        }

        public IEnumerable<Movie> GetStarMovies(Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock GetStarMovies");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetStarMovies");
                var starMovies = Context.GetStarMovies(selectedProfile).ToArray();
                Debug.WriteLine("unlock GetStarMovies");
                return starMovies;
            }
        }

        public IEnumerable<Movie> GetRecentlyPlayedMovies(Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock GetRecentlyPlayedMovies");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetRecentlyPlayedMovies");
                var recentlyPlayedMovies = Context.GetRecentlyPlayedMovies(selectedProfile).ToArray();
                Debug.WriteLine("unlock GetRecentlyPlayedMovies");
                return recentlyPlayedMovies;
            }
        }

        public IEnumerable<Movie> GetRecommendedMovies(Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock GetRecommendedMovies");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetRecommendedMovies");
                var recommendedMovies = Context.GetRecommendedMovies(selectedProfile).ToArray();
                Debug.WriteLine("unlock GetRecommendedMovies");
                return recommendedMovies;
            }
        }

        public void AddToPlaylist(IEnumerable<Music> musics, LibraryItem playlist)
        {
            Debug.WriteLine("Waiting for lock AddToPlaylist");
            lock (LockObject)
            {
                Debug.WriteLine("lock AddToPlaylist");
                foreach (var music in musics.Where(m => playlist.Musics.Any(pm => pm.Id == m.Id) == false))
                {
                    if (playlist.Musics.Contains(music) == false)
                        playlist.Musics.Add(music);
                }
                Context.Save();
                Debug.WriteLine("unlock AddToPlaylist");
            }
        }

        public void AddToPlaylist(string playlistId, IEnumerable<Music> musics)
        {
            Debug.WriteLine("Waiting for lock AddToPlaylist");
            lock (LockObject)
            {
                Debug.WriteLine("lock AddToPlaylist");
                var playlist = Context.LibraryItems.FirstOrDefault(o => o.Id == playlistId && o.Type == LibraryItemType.Playlist);
                if (playlist == null)
                {
                    Debug.WriteLine("unlock AddToPlaylist");
                    return;
                }
                AddToPlaylist(musics, playlist);
                Debug.WriteLine("unlock AddToPlaylist");
            }
        }

        public Profile GetProfile(string profileName)
        {
            Debug.WriteLine("Waiting for lock GetProfile");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetProfile");
                var profile = Context.Profiles.FirstOrDefault(p => p.Name == profileName);
                Debug.WriteLine("unlock GetProfile");
                return profile;
            }
        }

        public IEnumerable<Movie> GetAllMovies()
        {
            Debug.WriteLine("Waiting for lock GetAllMovies");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetAllMovies");
                var movies = Context.Movies.ToArray();
                Debug.WriteLine("unlock GetAllMovies");
                return movies;
            }
        }

        public bool IsProfileExists(string profileName)
        {
            Debug.WriteLine("Waiting for lock IsProfileExists");
            lock (LockObject)
            {
                Debug.WriteLine("lock IsProfileExists");
                var isProfileExists = Context.Profiles.Any(p => String.Compare(p.Name, profileName) == 0);
                Debug.WriteLine("unlock IsProfileExists");
                return isProfileExists;
            }
        }

        public Profile CreateProfile(string profileName)
        {
            Debug.WriteLine("Waiting for lock CreateProfile");
            lock (LockObject)
            {
                Debug.WriteLine("lock CreateProfile");
                var profile = new Profile
                                  {
                                      Id = Guid.NewGuid().ToString(),
                                      Name = profileName
                                  };
                Context.Profiles.AddObject(profile);
                Context.Save();
                Debug.WriteLine("unlock CreateProfile");
                return profile;
            }
        }

        public LibraryItem CreatePlaylist(string playlistTitle)
        {
            Debug.WriteLine("Waiting for lock CreatePlaylist");
            lock (LockObject)
            {
                Debug.WriteLine("lock CreatePlaylist");
                var libraryItem = Context.CreateLibraryItem(LibraryItemType.Playlist, playlistTitle);
                Context.Save();
                Debug.WriteLine("unlock CreatePlaylist");
                return libraryItem;
            }
        }

        public IEnumerable<LibraryItem> GetKnownLibraryItems(string type)
        {
            Debug.WriteLine("Waiting for lock GetKnownLibraryItems");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetKnownLibraryItems");
                var knownLibraryItems = Context.LibraryItems.Where(t => t.Type == type && t.Title != MediaAssistantSqliteDbEntities.Unknown).ToArray();
                Debug.WriteLine("unlock GetKnownLibraryItems");
                return knownLibraryItems;
            }
        }

        public void UpdateAndLink(Music music)
        {
            Debug.WriteLine("Waiting for lock UpdateAndLink");
            lock (LockObject)
            {
                Debug.WriteLine("lock UpdateAndLink");
                Context.UpdateMusic(music);
                Context.Save();
                Debug.WriteLine("unlock UpdateAndLink");
            }
        }

        public Movie UpdateMovie(Movie movie, JObject jsonObject)
        {
            Debug.WriteLine("Waiting for lock GetAUpdateMoviellPlaylists");
            lock (LockObject)
            {
                Debug.WriteLine("lock UpdateMovie");
                var updateMovie = Context.UpdateMovie(movie, jsonObject, true);
                Context.Save();
                Debug.WriteLine("unlock UpdateMovie");
                return updateMovie;
            }
        }

        public void UpdateTitles(bool showDrivesMoviesOnly, Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock UpdateTitles");
            lock (LockObject)
            {
                Debug.WriteLine("lock UpdateTitles");
                _processingMovies = Context.UpdateTitles(showDrivesMoviesOnly, selectedProfile).ToArray();
                Context.Save();
                Debug.WriteLine("unlock UpdateTitles");
            }
        }

        public LibraryItem GetYearLibraryItem(long year)
        {
            Debug.WriteLine("Waiting for lock GetYearLibraryItem");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetYearLibraryItem");
                var yearLibraryItem = Context.CreateLibraryItem(LibraryItemType.Year, year.ToString(CultureInfo.InvariantCulture));
                Debug.WriteLine("unlock GetYearLibraryItem");
                return yearLibraryItem;
            }
        }

        public IEnumerable<string> GetRecommendedMovieTitles(string title)
        {
            Debug.WriteLine("Waiting for lock GetRecommendedMovieTitles");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetRecommendedMovieTitles");
                var recommendedMovieTitles = Context.GetRecommendedMovieTitles(title);
                Debug.WriteLine("unlock GetRecommendedMovieTitles");
                return recommendedMovieTitles;
            }
        }

        public bool IsMovieExists(string movieTitle)
        {
            Debug.WriteLine("Waiting for lock IsMovieExists");
            lock (LockObject)
            {
                Debug.WriteLine("lock IsMovieExists");
                var isMovieExists = GetAllMovies().Any(m => String.Compare(m.Title, movieTitle, true) == 0);
                Debug.WriteLine("unlock IsMovieExists");
                return isMovieExists;
            }
        }

        public void CreateMovie(string movieTitle)
        {
            Debug.WriteLine("Waiting for lock CreateMovie");
            lock (LockObject)
            {
                Debug.WriteLine("lock CreateMovie");
                var movie = new Movie
                                {
                                    Id = Guid.NewGuid().ToString(),
                                    Title = movieTitle, 
                                    Status = MovieStatus.Pending, 
                                    WeightedRating = 0,
                                    CreateDate = DateTime.Now
                                };
                Context.Movies.AddObject(movie);
                Context.Save();
                Debug.WriteLine("unlock CreateMovie");
            }
        }

        public void CreateMoviesNotInRepository(IEnumerable<string> recommendedMovieTitles)
        {
            Debug.WriteLine("Waiting for lock CreateMoviesNotInRepository");
            lock (LockObject)
            {
                Debug.WriteLine("lock CreateMoviesNotInRepository");
                foreach (var title in recommendedMovieTitles)
                {
                    var movieTitle = title;
                    if (IsMovieExists(movieTitle) == false)
                    {
                        CreateMovie(movieTitle);
                    }
                }
                Debug.WriteLine("unlock CreateMoviesNotInRepository");
            }
        }

        public void AddMovie(Movie movie)
        {
            Debug.WriteLine("Waiting for lock AddMovie");
            lock (LockObject)
            {
                Debug.WriteLine("lock AddMovie");
                Context.AddMovie(movie);
                Debug.WriteLine("unlock AddMovie");
            }
        }

        public void UpdateProcessingTitle(bool showDrivesMoviesOnly)
        {
            Debug.WriteLine("Waiting for lock UpdateProcessingTitle");
            lock (LockObject)
            {
                Debug.WriteLine("lock UpdateProcessingTitle");
                _processingMovies = Context.UpdateProcessingTitle(showDrivesMoviesOnly).ToArray();
                Context.Save();
                Debug.WriteLine("unlock UpdateProcessingTitle");
            }
        }

        public void UpdateRecommendedMovieTitle(bool showDrivesMoviesOnly, Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock UpdateRecommendedMovieTitle");
            lock (LockObject)
            {
                Debug.WriteLine("lock UpdateRecommendedMovieTitle");
                Context.UpdateRecommendedMovieTitle(showDrivesMoviesOnly, selectedProfile);
                Context.Save();
                Debug.WriteLine("unlock UpdateRecommendedMovieTitle");
            }
        }

        public void UpdatePosterImage(Movie movie, byte[] bytes)
        {
            Debug.WriteLine("Waiting for lock UpdatePosterImage");
            lock (LockObject)
            {
                Debug.WriteLine("lock UpdatePosterImage");
                movie.PosterImage = bytes;
                Context.Save();
                Debug.WriteLine("unlock UpdatePosterImage");
            }
        }

        public IEnumerable<LibraryItem> GetOrderedChildren(LibraryItem libraryItem)
        {
            Debug.WriteLine("Waiting for lock GetOrderedChildren");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetOrderedChildren");
                var isLeafLibrary = libraryItem.Children.Any(o => o.Index == -1);
                var orderedChildren = isLeafLibrary ? libraryItem.Children.OrderBy(o => o.Title) : libraryItem.Children.OrderBy(o => o.Index);
                Debug.WriteLine("unlock GetOrderedChildren");
                return orderedChildren;
            }
        }

        public LibraryItem GetRatedLibraryItem(string rated)
        {
            Debug.WriteLine("Waiting for lock GetRatedLibraryItem");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetRatedLibraryItem");
                var ratedLibrary = GetFixedLibraryItem(LibraryItemType.RatedLibrary);
                var item = ratedLibrary.Children.FirstOrDefault(o => o.Title == rated);
                Debug.WriteLine("unlock GetRatedLibraryItem");
                return item;
            }
        }

        public void MarkAsPending(Movie movie)
        {
            Debug.WriteLine("Waiting for lock MarkAsPending");
            lock (LockObject)
            {
                Debug.WriteLine("lock MarkAsPending");
                if (movie.Status != MovieStatus.Creating)
                {
                    movie.Status = MovieStatus.Pending;
                    Context.Save();
                }
                Debug.WriteLine("unlock MarkAsPending");
            }
        }

        public ProfileMovie GetProfileMovie(Profile profile, Movie movie)
        {
            Debug.WriteLine("Waiting for lock GetProfileMovie");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetProfileMovie");
                var profileMovie = profile.ProfileMovies.FirstOrDefault(pm => pm.MovieId == movie.Id);
                Debug.WriteLine("unlock GetProfileMovie");
                return profileMovie;
            }
        }

        public void UpdateProfile(ProfileMovie profileMovie, int star, bool watched, bool isInWishList, bool isNew)
        {
            Debug.WriteLine("Waiting for lock UpdateProfile");
            lock (LockObject)
            {
                Debug.WriteLine("lock UpdateProfile");
                profileMovie.Star = star;
                profileMovie.Watched = watched;
                profileMovie.IsInWishList = isInWishList;
                profileMovie.Unread = isNew;
                Context.Save();
                Debug.WriteLine("lock UpdateProfile");
            }
        }

        public void MarkAsRead(Movie selectedMovie, Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock MarkAsRead");
            lock (LockObject)
            {
                Debug.WriteLine("lock MarkAsRead");
                var profileMovie = GetOrCreateProfileMovie(selectedProfile, selectedMovie);
                profileMovie.Unread = false;
                selectedMovie.IsNew = false;
                Context.Save();
                Debug.WriteLine("unlock MarkAsRead");
            }
        }

        public IEnumerable<Movie> GetMoviesNotWatched()
        {
            Debug.WriteLine("Waiting for lock GetMoviesNotWatched");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetMoviesNotWatched");
                var moviesNotWatched = GetAllMovies().Where(m => m.Watched == false).ToArray();
                Debug.WriteLine("unlock GetMoviesNotWatched");
                return moviesNotWatched;
            }
        }

        public IEnumerable<Movie> GetRecommendedMovies(string title)
        {
            Debug.WriteLine("Waiting for lock GetRecommendedMovies");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetRecommendedMovies");
                var recommendedMovieTitles = GetRecommendedMovieTitles(title).ToArray();
                CreateMoviesNotInRepository(recommendedMovieTitles);
                var recommendedMovies = Context.Movies.Where(m => recommendedMovieTitles.Contains(m.Title)).ToArray();
                Debug.WriteLine("unlock GetRecommendedMovies");
                return recommendedMovies;
            }
        }

        public void UpdateDuration(Music music, Duration duration)
        {
            Debug.WriteLine("Waiting for lock UpdateDuration");
            lock (LockObject)
            {
                Debug.WriteLine("lock UpdateDuration");
                if (music == null)
                {
                    Debug.WriteLine("unlock UpdateDuration");
                    return;
                }
                if (!String.IsNullOrWhiteSpace(music.TimeText) || !duration.HasTimeSpan)
                {
                    Debug.WriteLine("unlock UpdateDuration");
                    return;
                }
                music.Time = (decimal?)duration.TimeSpan.TotalMilliseconds;
                music.TimeText = ConvertTo.String(duration.TimeSpan);
                Context.Save();
                Debug.WriteLine("unlock UpdateDuration");
            }
        }

        public void AdvanceScore(Profile profile, Music music, double increment)
        {
            Debug.WriteLine("Waiting for lock AdvanceScore");
            lock (LockObject)
            {
                Debug.WriteLine("lock AdvanceScore");
                var profileMusic = GetProfileMusic(music, profile);
                if (profileMusic == null)
                {
                    Debug.WriteLine("unlock AdvanceScore");
                    return;
                }
                if (profileMusic.IsPlayingFirstTime())
                {
                    music.Score += (decimal)increment;
                }
                profileMusic.Score += (decimal)increment;
                Context.Save();
                Debug.WriteLine("unlock AdvanceScore");
            }
        }

        public LibraryItem GetNowPlayingPlaylist()
        {
            Debug.WriteLine("Waiting for lock GetNowPlayingPlaylist");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetNowPlayingPlaylist");
                var nowPlayingPlaylist = GetFixedLibraryItem(LibraryItemType.NowPlayingPlaylist);
                Debug.WriteLine("unlock GetNowPlayingPlaylist");
                return nowPlayingPlaylist;
            }
        }

        public bool IsNowPlayingMusic(Music music)
        {
            Debug.WriteLine("Waiting for lock IsNowPlayingMusic");
            lock (LockObject)
            {
                Debug.WriteLine("lock IsNowPlayingMusic");
                var isNowPlayingMusic = GetNowPlayingPlaylist().Musics.Any(m => m.Equals(music));
                Debug.WriteLine("unlock IsNowPlayingMusic");
                return isNowPlayingMusic;
            }
        }

        public double GetScore(Music music, Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock GetScore");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetScore");
                var profileMusic = music.ProfileMusics.FirstOrDefault(p => p.Profile == selectedProfile);
                double score = 0;
                if (profileMusic != null)
                {
                    score = (double)profileMusic.Score;
                }
                score += (double)music.Score;
                Debug.WriteLine("unlock GetScore");
                return score;
            }
        }

        public void UpdateRecommendation(Movie recommendedMovie, int totalRecommendation, Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock UpdateRecommendation");
            lock (LockObject)
            {
                Debug.WriteLine("lock UpdateRecommendation");
                var profileMovie = GetOrCreateProfileMovie(selectedProfile, recommendedMovie);
                profileMovie.Recommendation += totalRecommendation;
                Debug.WriteLine("unlock UpdateRecommendation");
            }
        }

        public void MarkAsFailed(Movie movie)
        {
            Debug.WriteLine("Waiting for lock MarkAsFailed");
            lock (LockObject)
            {
                Debug.WriteLine("lock MarkAsFailed");
                movie.Status = MovieStatus.Failed;
                Context.Save();
                Debug.WriteLine("unlock MarkAsFailed");
            }
        }

        public void MarkAsScanned(ScannedDirectory scannedDirectory)
        {
            Debug.WriteLine("Waiting for lock MarkAsScanned");
            lock (LockObject)
            {
                Debug.WriteLine("lock MarkAsScanned");
                scannedDirectory.LastScanned = DateTime.Now;
                Context.Save();
                Debug.WriteLine("unlock MarkAsScanned");
            }
        }

        public void SetMusicStatus(Music music, Music playingMusic)
        {
            Debug.WriteLine("Waiting for lock SetMusicStatus");
            lock (LockObject)
            {
                Debug.WriteLine("lock SetMusicStatus");
                if (music.Status == MusicStatus.Failed)
                {
                    Debug.WriteLine("unlock SetMusicStatus");
                }
                if (music.Equals(playingMusic))
                    music.Status = MusicStatus.Playing;
                else if (IsNowPlayingMusic(music))
                    music.Status = MusicStatus.NowPlaying;
                else
                {
                    music.Status = MusicStatus.NonPlaying;
                }
                Context.Save();
                Debug.WriteLine("unlock SetMusicStatus");
            }
        }

        public void SetNowPlayingPlaylist(IEnumerable<Music> resultMusics, IEnumerable<Music> selectedMusic, Music playingMusic)
        {
            Debug.WriteLine("Waiting SetNowPlayingPlaylist");
            lock (LockObject)
            {
                Debug.WriteLine("Locked SetNowPlayingPlaylist");
                if (selectedMusic.Count() == 1)
                {
                    selectedMusic = resultMusics;
                }
                var nowPlayingPlaylist = GetNowPlayingPlaylist();
                nowPlayingPlaylist.Musics.Clear();
                foreach (var music in resultMusics)
                {
                    music.Status = MusicStatus.NonPlaying;
                }
                foreach (var music in selectedMusic)
                {
                    if (File.Exists(music.FullPath))
                    {
                        nowPlayingPlaylist.Musics.Add(music);
                        music.Status = music.Equals(playingMusic) ? MusicStatus.Playing : MusicStatus.NowPlaying;
                    }
                    else
                    {
                        music.Status = MusicStatus.Failed;
                    }
                }

                Context.Save();
                Debug.WriteLine("Unlock SetNowPlayingPlaylist");
            }
        }

        public void IncrementPlayCount(Movie movie, Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock IncrementPlayCount");
            lock (LockObject)
            {
                Debug.WriteLine("lock IncrementPlayCount");
                movie.PlayCount++;
                var profileMovie = movie.GetProfileMovie(selectedProfile);
                if (profileMovie != null)
                {
                    profileMovie.PlayCount++;
                    profileMovie.LastPlayed = DateTime.Now;
                }
                Context.Save();
                Debug.WriteLine("unlock IncrementPlayCount");
            }
        }

        public void MarkAsProcessing(Movie movie)
        {
            Debug.WriteLine("Waiting for lock MarkAsProcessing");
            lock (LockObject)
            {
                Debug.WriteLine("lock MarkAsProcessing");
                if (movie.Status != MovieStatus.Creating)
                {
                    movie.Status = MovieStatus.Processing;
                    Context.Save();
                }
                Debug.WriteLine("unlock MarkAsProcessing");
            }
        }

        public bool HasAlternativeLocation(Movie selectedMovie)
        {
            Debug.WriteLine("Waiting for lock HasAlternativeLocation");
            lock (LockObject)
            {
                Debug.WriteLine("lock HasAlternativeLocation");
                var hasAlternativeLocation = selectedMovie.AlternativeLocations.Count > 0;
                Debug.WriteLine("unlock HasAlternativeLocation");
                return hasAlternativeLocation;
            }
        }

        public void UpdateMovieDetails(Movie movie)
        {
            Debug.WriteLine("Waiting for lock UpdateMovieDetails");
            lock (LockObject)
            {
                Debug.WriteLine("lock UpdateMovieDetails");
                if (movie == null)
                {
                    Debug.WriteLine("unlock UpdateMovieDetails");
                    return;
                }
                if (movie.Genres.Count == 0 || movie.Stars.Count == 0 || movie.Directors.Count == 0 || movie.Writers.Count == 0)
                {
                    movie.UpdateLibraryInformation();
                }
                Debug.WriteLine("unlock UpdateMovieDetails");
            }
        }

        public int GetMovieCount()
        {
            Debug.WriteLine("Waiting for lock GetMovieCount");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetMovieCount");
                var movieCount = Context.Movies.Count();
                Debug.WriteLine("unlock GetMovieCount");
                return movieCount;
            }
        }

        public int GetLibraryItemsCount()
        {
            Debug.WriteLine("Waiting for lock GetLibraryItemsCount");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetLibraryItemsCount");
                var libraryItemsCount = Context.LibraryItems.Count();
                Debug.WriteLine("unlock GetLibraryItemsCount");
                return libraryItemsCount;
            }
        }

        public void MakeDefaultLocation(Movie movie, string fullPath)
        {
            Debug.WriteLine("Waiting for lock MakeDefaultLocation");
            lock (LockObject)
            {
                Debug.WriteLine("lock MakeDefaultLocation");
                var alternativeLocation = movie.AlternativeLocations.FirstOrDefault(l => l.Location == fullPath);
                if (alternativeLocation == null)
                {
                    Debug.WriteLine("Unlock MakeDefaultLocation");
                    return;
                }
                var oldDefaultLocation = movie.FullPath;
                var fileInfo = new FileInfo(fullPath);
                movie.FullPath = fullPath;
                movie.FileName = fileInfo.Name;
                movie.Size = fileInfo.Length;
                movie.SizeText = Utility.GetFileSize(fileInfo.Length);
                alternativeLocation.Location = oldDefaultLocation;
                Context.Save();
                Debug.WriteLine("Unlock MakeDefaultLocation");
            }
        }

        public void UpdateMovieLocation(Movie movie, string fileName)
        {
            Debug.WriteLine("Waiting UpdateMovieLocation");
            lock (LockObject)
            {
                Debug.WriteLine("lock UpdateMovieLocation");
                var fileInfo = new FileInfo(fileName);
                movie.FullPath = fileName;
                movie.FileName = fileInfo.Name;
                movie.Size = fileInfo.Length;
                movie.SizeText = Utility.GetFileSize(fileInfo.Length);
                Context.Save();
                Debug.WriteLine("Unlock UpdateMovieLocation");
            }

        }

        public LibraryItem GetLibraryItem(string title, string type)
        {
            Debug.WriteLine("Waiting GetLibraryItem(string title, string type)");
            lock (LockObject)
            {
                Debug.WriteLine("Lock GetLibraryItem(string title, string type)");
                var libraryItem = Context.LibraryItems.FirstOrDefault(o => o.Type == type && o.Title == title);
                Debug.WriteLine("Unlock GetLibraryItem(string title, string type)");
                return libraryItem;
            }
        }

        public void UpdateFailedTitle(bool showDrivesMoviesOnly)
        {
            Debug.WriteLine("Waiting for lock UpdateFailedTitle");
            lock (LockObject)
            {
                Debug.WriteLine("lock UpdateFailedTitle");
                Context.UpdateFailedTitle(showDrivesMoviesOnly);
                Context.Save();
                Debug.WriteLine("unlock UpdateFailedTitle");
            }
        }

        public void UpdateMovieFileSize(Movie movie)
        {
            lock (LockObject)
            {
                if (File.Exists(movie.FullPath))
                {
                    var fi = new FileInfo(movie.FullPath);
                    movie.Size = fi.Length;
                    movie.SizeText = Utility.GetFileSize(fi.Length);
                    Context.Save();
                }
            }
        }

        public bool IsMusicFileAdded(string fileName)
        {
            lock (LockObject)
            {
                return Context.IsMusicFileAdded(fileName);
            }
        }

        public bool IsMovieFileAdded(string fileName)
        {
            lock (LockObject)
            {
                return Context.IsMovieFullPathAdded(fileName);
            }
        }

        public IEnumerable<Music> GetDeSyncedMusics()
        {
            lock (LockObject)
            {
                return Context.Musics.Where(m => m.TagStatus != TagStatus.Synced).ToArray();
            }
        }

        public void UpdateFileTag(Music music)
        {
            lock (LockObject)
            {
                Context.UpdateFileTag(music);
            }
        }

        public void UpdateTagAndLink(Music music)
        {
            lock (LockObject)
            {
                Context.UpdateTagAndLink(music);
            }
        }

        public void UpdateFileTagAndLink(Music music)
        {
            lock (LockObject)
            {
                Context.UpdateFileTag(music);
                Context.UnlinkMusic(music);
                Context.LinkMusic(music);
            }
        }

        public IEnumerable<Music> GetMusicWithInvalidData()
        {
            lock (LockObject)
            {
                return Context.Musics.Where(m => m.TagStatus ==TagStatus.InvalidData).ToArray();
            }
        }

        public IEnumerable<Music> GetMusicWithInvalidFile()
        {
            lock (LockObject)
            {
                return Context.Musics.Where(m => m.TagStatus == TagStatus.InvalidFile).ToArray().Where(IsInvalidFile);
            }
        }

        private static bool IsInvalidFile(Music m)
        {
            return m.TagStatus == TagStatus.InvalidFile && File.Exists(m.FullPath) && String.Compare(Path.GetExtension(m.FullPath), "mp3", StringComparison.OrdinalIgnoreCase)==0;
        }

        public IEnumerable<Movie> GetRecentlyAddedMovies(Profile selectedProfile)
        {
            Debug.WriteLine("Waiting for lock GetRecentlyAddedMovies");
            lock (LockObject)
            {
                Debug.WriteLine("lock GetRecentlyAddedMovies");
                var recentlyPlayedMovies = Context.GetRecentlyAddedMovies(selectedProfile).ToArray();
                Debug.WriteLine("unlock GetRecentlyAddedMovies");
                return recentlyPlayedMovies;
            }
        }

        public void Remove(IEnumerable<Music> musics)
        {
            lock (LockObject)
            {
                foreach (var music in musics)
                {
                    Context.UnlinkMusic(music);
                    Context.Musics.DeleteObject(music);    
                }
                Context.Save();    
            }
        }
    }
}
